﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Collections;

using BBCommon;
using Lidgren.Library.Network;

namespace Server
{
   public class BBServer
   {
      public NetLog Log;
      public NetServer Server;
      public NetAppConfiguration Config;

      protected PlayerList PList;

      /// Event fired every time a client connects
      public event EventHandler<ClientConnectEventArgs> ClientConnected;
      /// Event fired every time a client disconnects
      public event EventHandler<ClientConnectEventArgs> ClientDisconnected;

      public BBServer()
      {
         // Create a log
         Log = new NetLog();
         // Ignore net events of type 'verbose'
         Log.IgnoreTypes = NetLogEntryTypes.Verbose; // Quiet, you !
         // Save log output to this file
         Log.OutputFileName = "serverlog.html";

         // Create the player list
         PList = new PlayerList();
      }

      public void Loop()
      {
         if (Server != null)
         {
            // Call heartbeat; this sends queued packets, keepalives and acknowledges etc.
            Server.Heartbeat();

            NetMessage msg;
            // Read a packet if available
            while ((msg = Server.ReadMessage()) != null)
            {
               // Deserialize message & pass it to the server
               try
               {
                  BBMessage bbmsg = BBMessage.FromNetMessage(msg);
                  HandleMessage(bbmsg, msg.Sender);
               }
               catch (Exception ex)
               {
                  Log.Error("Deserialization error : " + ex.Message);
               }
            }
         }
      }

      // Broadcast a message to logged in players, except the id player (if id = -1, no player is ignored)
      public void BroadcastMsgExcept(NetMessage msg, NetChannel channel, int id)
      {
         IDictionaryEnumerator en = PList.GetPlayerEnum();
         while (en.MoveNext())
         {
            if (((Player)en.Value).Carac.ActorID != id)
               Server.SendMessage(msg, ((Player)en.Value).Connection, channel);
         }
      }

      #region Server Invoked Methods
      public void StartServer(int Port)
      {
         // Create a configuration for the server
         // 'SimpleChat' is our application identifier, which distinguishes it from other
         // lidgren.library.network applications
         // 14242 is the port the server listens on; ie. the port clients should connect to
         Config = new NetAppConfiguration("BBTcpTest", Port);

         // set maximum number of clients to 128
         Config.MaximumConnections = 128;

         // set a server name (which shows up when client discovers local servers)
         Config.ServerName = "BB Tcp Test Server";

         // enable encryption; this key was generated using the 'GenerateEncryptionKeys' application
         Config.EnableEncryption(
              "AQABwV1rQ1NDWzkkhUsYtMiRGjyxSn/zI13tros2RlXHlW6PA7Cvw2nOyMhDmweM+" +
              "T/+FWzssAWLh8qd+YHaMCCFskVdaZfRzquZlNOs9mX/vtxkLrXgBUaJQD/XOuBeJF" +
              "o3RfAKA4uhrEr7Bk1iB3zJQW1bSHK7KmmEWzMErk6iA7c=",
              "R6Gp2LkBnMugYdieAGrEhimxq92WXqWvJRfhNVEVrJ/WTxGnKwcJWYwGr2muqa8xG" +
              "xLM47BKLjW7K/EUuPtzSx8cGCDG1ET+5C984lu+pxb1XTHhz4qWpfS8Ti7BV4472F" +
              "wK9y/SxVew19rXNvYLD4aZrVOel+UV8a+3SJTGYXGyEX0OqmoaH0KPfGH5nGLOXcm" +
              "ZHy8yarV193X+bwQL8zxMbNk2UntbLg4m5+t2XyPY2cexDLLCWPviqbF4ZbLJO8xs" +
              "uni1H2QJZ9wn61fNhOQBkoI8YfYYah257zYDVCBgpDFPYXH5LaWfhevV3534Sz4Cw" +
              "D7r6m5aCD0Tsx9pDfsa81VyaJO37kpMTK7HvNtGv5GXwWTNNGwUQTfRIDE06RmLvv" +
              "zyC5YCT40Nqwjz5/dN4Qgb/dAUvsQYn3zsZNj7lfzurcLxGG4x0aCOVafeAb5oRHr" +
              "4VthHVNjymDmiPaiZyP8fmX10M+3hl1o/LDf71jEW+MqPiMgoYpGDEccFxMHtodV6" +
              "Yndv/z+4iFZ76K3Z/QQFgOSaVoAdx2VjlaCXaX6Kywng/pIf3mumy5WubTnjcdG7f" +
              "J+ImyVzs9lkiw==");

         // Now create the server!
         Server = new NetServer(Config, Log);

         //// catch clients connecting
         //ServerBB.Server = Program.Server;
         //Server.ConnectionRequest += new EventHandler<NetConnectRequestEventArgs>(Program.Server_ConnectionRequest);

         // catch status changes; for example, when a client connects or disconnects
         Server.StatusChanged += new EventHandler<NetStatusEventArgs>(this.Server_StatusChanged);
      }

      public void Stop(string reason)
      {
         if (Server != null)
            Server.Shutdown(reason);
      }

      public void KickUser(string nick, string reason)
      {
         if (PList.GetPlayer(nick, null) != null)
            KickUser(PList.GetPlayer(nick, null).Carac.ActorID, reason);
      }

      // Send a chat to a user. if nick is an invalid player (null), message will be broadcasted
      public void SendChat(string nick, string msg)
      {
         Player player = PList.GetPlayer(nick, null);
         if (player != null)
         {            
            BBMessage bbmsg = new BBMessage();
            // Set the id of the message sender
            bbmsg.MsgChat(msg, player.Carac);
            bbmsg.ActorInfo = new ActorRTInfo();
            bbmsg.ActorInfo.Id = -1;  // Nobody
            
            if (player == null)
            {
               // Broadcast this message
               Server.Broadcast(bbmsg.GetNetMessage(), NetChannel.ReliableUnordered);
            }
            else
            {
               Server.SendMessage(bbmsg.GetNetMessage(), player.Connection, NetChannel.ReliableUnordered);
            }
         }
      }

      public string GetUserInfo(string nick)
      {
         Player player = Program.ServerBB.PList.GetPlayer(nick, null);
         string text;
         text = "Nick: " + player.Carac.Nick + ". ID: " + player.Carac.ActorID + Environment.NewLine;
         text += "Ping: " + player.Connection.AverageRoundtripTime * 1000 + "ms." + Environment.NewLine;
         text += Environment.NewLine;
         text += "Health: " + player.Info.Health + "/" + player.Carac.HealthCap + Environment.NewLine;
         text += "Mana: " + player.Info.Mana + "/" + player.Carac.ManaCap + Environment.NewLine;
         text += "Weapon: " + player.Info.Weapon + Environment.NewLine;

         return text;
      }

      public void KickUser(int id, string reason)
      {
         NetConnection net = PList.GetPlayer(id).Connection;

         Log.Info("Client " + id + " kicked : " + reason);

         // Notify the user
         net.Disconnect("Kicked ! " + reason);
      }

      public void Server_StatusChanged(object sender, NetStatusEventArgs e)
      {
         // If a client disconnects, delete it from the list
         switch (e.Connection.Status)
         {
            case NetConnectionStatus.Disconnected:
               if (e.Connection.Tag != null)
               {
                  int id = PlayerList.GetPlayerID(e.Connection);
                  Log.Info("Client " + id + " disconnected : " + e.Reason);

                  // Notify other clients
                  BBMessage msg_back = new BBMessage();
                  msg_back.MsgDelPlayer(id, e.Reason);
                  BroadcastMsgExcept(msg_back.GetNetMessage(), NetChannel.ReliableUnordered, id);

                  // Notify
                  NotifyClientDisconnected(e.Connection);
                  // Delete the player from our list
                  PList.DelPlayer(PlayerList.GetPlayerID(e.Connection));
               }
               break;

            default:
               // display changes
               Log.Info(e.Connection + ": " + e.Connection.Status + " - " + e.Reason);
               break;
         }
      }

      #endregion

      #region Message Handling
      public void HandleMessage(BBMessage msg, NetConnection sender)
      {
         switch (msg.MsgType)
         {
            case BBMsgType.ASK_LOGIN:
               HdlLogin(msg, sender);
               break;
            case BBMsgType.PRIVATE_MESSAGE:
            case BBMsgType.PRIVATE_CONVERSATION:
            case BBMsgType.JOIN_CONVERSATION:
            case BBMsgType.LEAVE_CONVERSATION:
            case BBMsgType.CHAT:
            case BBMsgType.VDM:
            case BBMsgType.PENDU:
            case BBMsgType.BASH:
            case BBMsgType.TYPING_MSG:
            case BBMsgType.KNOCK:
               HdlMsgReBroadcast(msg, sender);
               break;
            case BBMsgType.COFFEE:
               HdlMsgReBroadcast(msg, sender);
               break;
            case BBMsgType.EAT:
               HdlMsgReBroadcast(msg, sender);
               break;
            case BBMsgType.ACTOR_SHOOT:
               HdlMsgShoot(msg, sender);
               break;

            case BBMsgType.ACTOR_SHOOT_RESPONSE:
               HdlMsgShoot(msg, sender);
               break;

            default:
               Log.Warning("Received unknown message");
               break;
         }
      }

      private void HdlMsgShoot(BBMessage msg, NetConnection sender)
      {
         int id = PlayerList.GetPlayerID(sender);

         if (msg.MsgType == BBMsgType.ACTOR_SHOOT)
         {
            Log.Info("Shoot from " + id + " to " + msg.Shoot.Cible_ID);
            ++PList.GetPlayer(id).score.Shots;
         }
         else if (msg.MsgType == BBMsgType.ACTOR_SHOOT_RESPONSE)
         {
            Log.Info("Shoot response from " + id + " - shooter: " + msg.Resp.SenderID + ". result : " + msg.Resp.ShootResponse.ToString());

            if (msg.Resp.ShootResponse == ShootResponseDesc.Response.AVOIDED || msg.Resp.ShootResponse == ShootResponseDesc.Response.COUNTERED)
            {
               ++PList.GetPlayer(id).score.Hit;
            }
            else if (msg.Resp.ShootResponse == ShootResponseDesc.Response.TAKEN)
            {
               ++PList.GetPlayer(id).score.Hit;
               ++PList.GetPlayer(id).score.Deaths;
               ++PList.GetPlayer(msg.Resp.SenderID).score.Kills;
            }
         }

         // Set the id of the message sender
         msg.ActorInfo = new ActorRTInfo();
         msg.ActorInfo.Id = id;

         // And broadcast this message
         Server.Broadcast(msg.GetNetMessage(), NetChannel.ReliableUnordered);
      }

      protected void HdlLogin(BBMessage msg, NetConnection sender)
      {         
         // Test if login is possible
         if (PList.GetPlayer(msg.PlayerCarac.Nick, null) != null)
         {
            // No : player already exists   
            BBMessage msg_back = new BBMessage();
            msg_back.MsgNoLogin("Player \"" + msg.PlayerCarac.Nick + "\" already exists");
            Server.SendMessage(msg_back.GetNetMessage(), sender, NetChannel.ReliableUnordered);

            // And close the connection
            sender.Disconnect("Player \"" + msg.PlayerCarac.Nick + "\" already exists, Login refused");
         }
         else
         {
            if ((msg.Param1 == null) || (msg.Param1 != BBMessage.Version))
            {
               BBMessage msg_back1 = new BBMessage();
               string str = msg.Param1;
               if (str == null)
                  str = "No version";

               msg_back1.MsgNoLogin("Player \"" + msg.PlayerCarac.Nick + "\" , Login refused. Version Serveur = " + BBMessage.Version + " Version Client = " + str);
               Server.SendMessage(msg_back1.GetNetMessage(), sender, NetChannel.ReliableUnordered);

               //sender.Disconnect("Player \"" + msg.PlayerCarac.Nick + "\" , Login refused. Version Serveur = " + BBMessage.Version + " Version Client = " + msg.Param1.ToString());
            }
            else
            {
               // Yes : Add the player to the list and send OK
               // Now add the player to our list
               int id = PList.CreatePlayer(msg.PlayerCarac, sender);
               Log.Info("Player added : " + msg.PlayerCarac.Nick + ", " + id);

               // Send OK
               msg.MsgOkLogin(id);
               Server.SendMessage(msg.GetNetMessage(), sender, NetChannel.ReliableUnordered);

               // Send connection of other clients to our new client
               IDictionaryEnumerator en = PList.GetPlayerEnum();
               BBMessage msg_notice;
               while (en.MoveNext())
               {
                  if (((Player)en.Value).Carac.ActorID != id)
                  {
                     msg_notice = new BBMessage();
                     msg_notice.MsgAddPlayer((Player)en.Value, false);
                     Server.SendMessage(msg_notice.GetNetMessage(), sender, NetChannel.ReliableUnordered);
                  }

               }

               // Notify other clients
               BBMessage msg_back = new BBMessage();
               msg_back.MsgAddPlayer(PList.GetPlayer(id), true);
               BroadcastMsgExcept(msg_back.GetNetMessage(), NetChannel.ReliableUnordered, id);

               // Notify
               NotifyClientConnected(id);
            }
         }
      }
      protected void HdlMsgReBroadcast(BBMessage msg, NetConnection sender)
      {
         int id = PlayerList.GetPlayerID(sender);

         //if (msg.MsgType == BBMsgType.CHAT)
         //   Log.Info("Chat " + id + " : " + msg.Param1);

         // Set the id of the message sender
         msg.ActorInfo = new ActorRTInfo();
         msg.ActorInfo.Id = id;

         // And broadcast this message
         Server.Broadcast(msg.GetNetMessage(), NetChannel.ReliableUnordered);
      }
      #endregion

      #region Event Firing
      internal void NotifyClientConnected(int id)
      {
         if (ClientConnected != null)
         {
            ClientConnected(this, new ClientConnectEventArgs(PList.GetPlayer(id).Carac));
         }
      }
      internal void NotifyClientDisconnected(NetConnection co)
      {
         if (ClientDisconnected != null)
         {
            ClientDisconnected(this, new ClientConnectEventArgs(PList.GetPlayer(PlayerList.GetPlayerID(co)).Carac));
         }
      }
      #endregion

      internal void SendStats()
      {
         string stats = ">> Stats" + Environment.NewLine + "Success | Throws - Hits | Received";
         string form = "D3";

         // Send connection of other clients to our new client
         IDictionaryEnumerator en = PList.GetPlayerEnum();
         while (en.MoveNext())
         {
            stats += Environment.NewLine;

            PlayerScore s = ((Player)en.Value).score;
            stats += "       " + s.Kills.ToString(form) + " | " + s.Shots.ToString(form) + "       - "
                               + s.Deaths.ToString(form) + " | " + s.Hit.ToString(form)
                            + " -- " + ((Player)en.Value).Carac.Nick;
         }

         SendChat(null, stats);
      }
   }

   public sealed class ClientConnectEventArgs : EventArgs
   {
      public PlayerCarac Player;

      public ClientConnectEventArgs(PlayerCarac player)
      {
         Player = player;
      }
   }
}
